{
  "name": "Case 73 - Operations Manager Company Shuttle Service Feedback",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 9
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -912,
        -192
      ],
      "id": "c1ba3c5b-5e6f-41c0-88f5-c31a105863c8",
      "name": "Weekly Schedule - Monday 9 AM"
    },
    {
      "parameters": {
        "actorId": {
          "__rl": true,
          "value": "Xb8osYTtOjlsgI6k9",
          "mode": "id",
          "cachedResultName": "Google Maps Scraper"
        },
        "customBody": "{\n  \"language\": \"en\",\n  \"maxReviews\": 100,\n  \"personalData\": true,\n  \"startUrls\": [\n    {\n      \"url\": \"https://www.google.com/maps/place/Mumbai+Airport+Taxi/@19.1486993,72.8334846,17z/data=!3m1!4b1!4m6!3m5!1s0x3be7b7167093997f:0x6f77428361fcb10a!8m2!3d19.1486943!4d72.8360595!16s%2Fg%2F11g0ts4zvv?entry=ttu&g_ep=EgoyMDI1MTIwOS4wIKXMDSoASAFQAw%3D%3D\"\n    }\n  ],\n  \"reviewsSort\": \"newest\",\n  \"reviewsOrigin\": \"all\"\n}"
      },
      "type": "@apify/n8n-nodes-apify.apify",
      "typeVersion": 1,
      "position": [
        -720,
        -192
      ],
      "id": "62cae2a7-d063-4d67-88bc-56d47a14292b",
      "name": "Scrape Shuttle Service Reviews",
      "credentials": {
        "apifyApi": {
          "id": "w5S6YBbbyUddEfQA",
          "name": "Apify account"
        }
      }
    },
    {
      "parameters": {
        "resource": "Datasets",
        "datasetId": "={{ $json.defaultDatasetId }}",
        "limit": 100
      },
      "type": "@apify/n8n-nodes-apify.apify",
      "typeVersion": 1,
      "position": [
        -528,
        -192
      ],
      "id": "5c326740-4dcc-4b72-b50e-a3a83f40b88b",
      "name": "Get Dataset Items",
      "credentials": {
        "apifyApi": {
          "id": "w5S6YBbbyUddEfQA",
          "name": "Apify account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Filter out reviews without meaningful text\nconst items = $input.all();\nconst validItems = [];\n\nitems.forEach((item, index) => {\n  const text = item.json.text || item.json.textTranslated || '';\n  \n  // Skip if text is empty or too short\n  if (text.trim().length < 10) {\n    console.log(`Skipping item ${index}: Empty or too short review text`);\n    return;\n  }\n  \n  validItems.push(item);\n});\n\nconsole.log(`Filtered: ${items.length} total → ${validItems.length} valid reviews with text`);\n\nreturn validItems;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -336,
        -192
      ],
      "id": "66fb4eb5-9d96-4b39-8e7b-098eaaab97dc",
      "name": "Validate Review Text"
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "gpt-4o",
          "mode": "list",
          "cachedResultName": "GPT-4o"
        },
        "responses": {
          "values": [
            {
              "role": "system",
              "content": "You are a Corporate Transportation Analyst. Analyze employee reviews about company shuttle service. Identify both positive aspects and issues.\n\nReturn ONLY this exact JSON format with NO other text:\n\n{\n  \"feedback_category\": \"Schedule Reliability|Driver Courtesy|Cleanliness|Route Efficiency|Crowding Issues|Communication|Other\",\n  \"sentiment\": \"positive|negative|neutral|mixed\",\n  \"actionability\": \"Schedule Adjustment|Add Capacity|Driver Recognition|Route Optimization|Communication Improvement|Maintenance Issue|No Action Needed\",\n  \"key_feedback\": \"specific observation from review\",\n  \"key_quote\": \"most important sentence from review\"\n}\n\nCATEGORIES:\n\nSchedule Reliability = On-time performance, consistency, delays\nKeywords: always late, on time, delayed, runs like clockwork, unpredictable\nExample: \"Shuttle always 10 minutes late\" → Schedule Reliability\n\nDriver Courtesy = Driver behavior, helpfulness, professionalism\nKeywords: driver is great, rude driver, helpful, friendly, professional\nExample: \"Driver John is amazing, always friendly\" → Driver Courtesy\n\nCleanliness = Vehicle condition, hygiene, maintenance\nKeywords: clean, dirty, well-maintained, smells bad, spotless\nExample: \"Shuttle is always clean\" → Cleanliness\n\nRoute Efficiency = Route planning, stops, travel time\nKeywords: too many stops, efficient route, takes forever, direct route\nExample: \"Too many stops, takes 45 minutes\" → Route Efficiency\n\nCrowding Issues = Capacity, overcrowding, seating availability\nKeywords: packed, crowded, standing room only, always full, plenty of space\nExample: \"Packed during rush hour, standing room only\" → Crowding Issues\n\nCommunication = Schedule updates, delay notifications, information sharing\nKeywords: no updates, good communication, app is helpful, never notified\nExample: \"No notification when shuttle is delayed\" → Communication\n\nOther = Everything else\n\nSENTIMENT:\n- positive: Praise, satisfaction, appreciation\n- negative: Complaints, dissatisfaction, problems\n- neutral: Factual observations, no strong opinion\n- mixed: Both positive and negative elements\n\nACTIONABILITY:\n- Schedule Adjustment: Timing changes needed\n- Add Capacity: More buses/larger vehicles needed\n- Driver Recognition: Positive driver mentions for awards\n- Route Optimization: Route changes suggested\n- Communication Improvement: Better rider communication needed\n- Maintenance Issue: Vehicle repair/cleaning needed\n- No Action Needed: General observation, no action required\n\nReturn ONLY the JSON. No explanation. Start with { and end with }"
            },
            {
              "content": "=Analyze this shuttle service review:\n\nService: {{ $json.title }}\nEmployee: {{ $json.name }}\nRating: {{ $json.stars }} stars\nDate: {{ $json.publishedAtDate }}\nReview: {{ $json.text }}\n\nReturn only JSON."
            }
          ]
        },
        "builtInTools": {},
        "options": {
          "maxTokens": 500,
          "temperature": 0
        }
      },
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 2.1,
      "position": [
        -80,
        -192
      ],
      "id": "1684ad07-1d0b-49dd-b478-dc598d0c323c",
      "name": "AI: Analyze Shuttle Feedback",
      "credentials": {
        "openAiApi": {
          "id": "ICwxUBbatsF2sDvy",
          "name": "OpenAi account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// ENHANCED PARSER WITH VALIDATION\nconst items = [];\nconst input = $input.all();\n\nfunction extractJSON(text) {\n  const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n  if (!jsonMatch) return null;\n  return jsonMatch[0];\n}\n\ninput.forEach((item, index) => {\n  try {\n    let aiText = item.json.output[0].content[0].text || '';\n    \n    // Clean markdown and extra text\n    aiText = aiText\n      .replace(/```json/gi, '')\n      .replace(/```/g, '')\n      .trim();\n    \n    // Extract JSON\n    const jsonStr = extractJSON(aiText);\n    \n    if (!jsonStr) {\n      throw new Error('No JSON found in response');\n    }\n    \n    // Parse\n    const parsed = JSON.parse(jsonStr);\n    \n    // Validate required fields\n    if (!parsed.feedback_category || !parsed.sentiment || !parsed.actionability) {\n      throw new Error('Missing required fields');\n    }\n    \n    items.push({ json: parsed });\n    \n  } catch (error) {\n    console.error(`Parse error for item ${index}:`, error.message);\n    \n    items.push({\n      json: {\n        feedback_category: 'Other',\n        sentiment: 'neutral',\n        actionability: 'No Action Needed',\n        key_feedback: 'Parse error',\n        key_quote: ''\n      }\n    });\n  }\n});\n\nreturn items;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        256,
        -192
      ],
      "id": "23417e97-1b88-4b4a-b9e1-b626cb5b245d",
      "name": "Parse AI Response"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cat",
              "name": "feedback_category",
              "value": "={{ $json.feedback_category }}",
              "type": "string"
            },
            {
              "id": "sent",
              "name": "sentiment",
              "value": "={{ $json.sentiment }}",
              "type": "string"
            },
            {
              "id": "action",
              "name": "actionability",
              "value": "={{ $json.actionability }}",
              "type": "string"
            },
            {
              "id": "feedback",
              "name": "key_feedback",
              "value": "={{ $json.key_feedback && $json.key_feedback.includes('Parse error') ? 'Not sufficient data available' : $json.key_feedback }}",
              "type": "string"
            },
            {
              "id": "quote",
              "name": "key_quote",
              "value": "={{ $json.key_quote && $json.key_quote !== '' ? $json.key_quote : 'No quote available' }}",
              "type": "string"
            },
            {
              "id": "date",
              "name": "review_date",
              "value": "={{ $('Validate Review Text').item.json.publishedAtDate }}",
              "type": "string"
            },
            {
              "id": "employee",
              "name": "employee_name",
              "value": "={{ $('Validate Review Text').item.json.name }}",
              "type": "string"
            },
            {
              "id": "rating",
              "name": "star_rating",
              "value": "={{ $('Validate Review Text').item.json.stars }}",
              "type": "number"
            },
            {
              "id": "text",
              "name": "review_text",
              "value": "={{ $('Validate Review Text').item.json.textTranslated || $('Validate Review Text').item.json.text }}",
              "type": "string"
            },
            {
              "id": "url",
              "name": "review_url",
              "value": "={{ $('Validate Review Text').item.json.reviewUrl }}",
              "type": "string"
            },
            {
              "id": "service",
              "name": "service_name",
              "value": "={{ $('Validate Review Text').item.json.address }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        480,
        -192
      ],
      "id": "f6c63fea-24b0-459a-8d71-f131cfcc477f",
      "name": "Edit Fields"
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "18ZMqipj7g8UNy60zwwhLq7pRRHgq83wKOe_3Jr0ojr8",
          "mode": "list",
          "cachedResultName": "Case 73 - Shuttle sheet log",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/18ZMqipj7g8UNy60zwwhLq7pRRHgq83wKOe_3Jr0ojr8/edit?usp=drivesdk"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Sheet1",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/18ZMqipj7g8UNy60zwwhLq7pRRHgq83wKOe_3Jr0ojr8/edit#gid=0"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "Review_Date": "={{ $json.review_date }}",
            "Employee_Name": "={{ $json.employee_name }}",
            "Star_Rating": "={{ $json.star_rating }}",
            "Review_Text": "={{ $json.review_text }}",
            "Feedback_Category": "={{ $json.feedback_category }}",
            "Sentiment": "={{ $json.sentiment }}",
            "Actionability": "={{ $json.actionability }}",
            "Key_Feedback": "={{ $json.key_feedback }}",
            "Key_Quote": "={{ $json.key_quote }}",
            "Review_URL": "={{ $json.review_url }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "Review_Date",
              "displayName": "Review_Date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Employee_Name",
              "displayName": "Employee_Name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Star_Rating",
              "displayName": "Star_Rating",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Review_Text",
              "displayName": "Review_Text",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Feedback_Category",
              "displayName": "Feedback_Category",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Sentiment",
              "displayName": "Sentiment",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Actionability",
              "displayName": "Actionability",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Key_Feedback",
              "displayName": "Key_Feedback",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Key_Quote",
              "displayName": "Key_Quote",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Review_URL",
              "displayName": "Review_URL",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        672,
        -192
      ],
      "id": "09d59362-df42-496f-b1ce-db6e65c41c7a",
      "name": "Log to Google Sheet",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "LOs2dbk9lby0NfDM",
          "name": "Google Sheets account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const items = $('Edit Fields').all();\n\nif (items.length === 0) {\n  return [{ json: { total_reviews: 0, avg_rating: 0, category_breakdown: {}, sentiment_breakdown: {}, actionability_breakdown: {}, top_positive: [], top_negative: [], service_name: 'Unknown' } }];\n}\n\nconst categoryCount = {};\nconst sentimentCount = {};\nconst actionabilityCount = {};\nconst positiveItems = [];\nconst negativeItems = [];\nlet totalRating = 0;\nconst service_name = items[0].json.service_name || 'Unknown';\n\nitems.forEach(item => {\n  const data = item.json;\n  const category = data.feedback_category || 'Other';\n  categoryCount[category] = (categoryCount[category] || 0) + 1;\n  const sentiment = data.sentiment || 'neutral';\n  sentimentCount[sentiment] = (sentimentCount[sentiment] || 0) + 1;\n  const action = data.actionability || 'No Action Needed';\n  actionabilityCount[action] = (actionabilityCount[action] || 0) + 1;\n  \n  totalRating += data.star_rating || 0;\n  \n  const feedbackItem = {\n    feedback: data.key_feedback || '',\n    quote: data.key_quote || '',\n    employee: data.employee_name || 'Anonymous',\n    date: data.review_date || '',\n    rating: data.star_rating || 0,\n    url: data.review_url || '',\n    category: category,\n    actionability: action\n  };\n  \n  if (sentiment === 'positive') {\n    positiveItems.push(feedbackItem);\n  } else if (sentiment === 'negative') {\n    negativeItems.push(feedbackItem);\n  }\n});\n\n// Sort by rating\npositiveItems.sort((a, b) => b.rating - a.rating);\nnegativeItems.sort((a, b) => a.rating - b.rating);\n\nconst topPositive = positiveItems.slice(0, 5);\nconst topNegative = negativeItems.slice(0, 5);\nconst avgRating = items.length > 0 ? (totalRating / items.length).toFixed(2) : 0;\n\nreturn [{ json: { total_reviews: items.length, avg_rating: avgRating, category_breakdown: categoryCount, sentiment_breakdown: sentimentCount, actionability_breakdown: actionabilityCount, top_positive: topPositive, top_negative: topNegative, service_name: service_name } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        864,
        -192
      ],
      "id": "2137b336-7872-4599-9ec4-05c23193614e",
      "name": "Aggregate Feedback Summary"
    },
    {
      "parameters": {
        "sendTo": "operationsmanager@company.com",
        "subject": "=Weekly Shuttle Service Report - {{ $json.service_name }}",
        "emailType": "text",
        "message": "=WEEKLY SHUTTLE SERVICE FEEDBACK REPORT\n=========================================\nService: {{ $json.service_name }}\n\n📊 OVERVIEW:\nTotal Reviews Analyzed: {{ $json.total_reviews }}\nAverage Rating: {{ $json.avg_rating }} / 5.0 stars\n\nFEEDBACK CATEGORIES:\n{{ Object.keys($json.category_breakdown).map(cat => cat + ': ' + $json.category_breakdown[cat] + ' mentions').join('\\n') }}\n\nSENTIMENT BREAKDOWN:\n{{ Object.keys($json.sentiment_breakdown).map(sent => sent.toUpperCase() + ': ' + $json.sentiment_breakdown[sent]).join('\\n') }}\n\nACTIONABLE ITEMS:\n{{ Object.keys($json.actionability_breakdown).map(action => action + ': ' + $json.actionability_breakdown[action]).join('\\n') }}\n\n✅ TOP 5 POSITIVE HIGHLIGHTS:\n{{ $json.top_positive.map((item, i) => (i+1) + '. [' + item.category + '] - ⭐' + item.rating + '/5\\n   Feedback: ' + item.feedback + '\\n   Quote: \"' + item.quote + '\"\\n   - ' + item.employee + ' | ' + item.date).join('\\n\\n') }}\n\n❌ TOP 5 AREAS FOR IMPROVEMENT:\n{{ $json.top_negative.map((item, i) => (i+1) + '. [' + item.category + '] - ⭐' + item.rating + '/5\\n   Feedback: ' + item.feedback + '\\n   Quote: \"' + item.quote + '\"\\n   - ' + item.employee + ' | ' + item.date).join('\\n\\n') }}\n\nRECOMMENDED ACTIONS:\n\n{{ $json.actionability_breakdown['Schedule Adjustment'] ? '⏰ SCHEDULE: ' + $json.actionability_breakdown['Schedule Adjustment'] + ' reviews mention timing issues - Review departure times and frequency' : '' }}\n\n{{ $json.actionability_breakdown['Add Capacity'] ? '🚌 CAPACITY: ' + $json.actionability_breakdown['Add Capacity'] + ' reviews mention crowding - Consider additional buses during peak hours' : '' }}\n\n{{ $json.actionability_breakdown['Driver Recognition'] ? '⭐ DRIVER RECOGNITION: ' + $json.actionability_breakdown['Driver Recognition'] + ' positive driver mentions - Recommend employee recognition program' : '' }}\n\n{{ $json.actionability_breakdown['Route Optimization'] ? '🗺️ ROUTE: ' + $json.actionability_breakdown['Route Optimization'] + ' reviews suggest route improvements - Analyze stop efficiency' : '' }}\n\n{{ $json.actionability_breakdown['Communication Improvement'] ? '📱 COMMUNICATION: ' + $json.actionability_breakdown['Communication Improvement'] + ' reviews mention lack of updates - Improve real-time notifications' : '' }}\n\n{{ $json.actionability_breakdown['Maintenance Issue'] ? '🔧 MAINTENANCE: ' + $json.actionability_breakdown['Maintenance Issue'] + ' reviews mention vehicle condition - Schedule deep cleaning/repairs' : '' }}\n\nCONTRACT RENEWAL CONSIDERATIONS:\n{{ $json.sentiment_breakdown.positive > $json.sentiment_breakdown.negative ? '✅ Overall positive sentiment - Service provider meeting expectations' : '⚠️ Negative sentiment exceeds positive - Consider vendor performance review' }}\n{{ parseFloat($json.avg_rating) >= 4.0 ? '✅ High average rating - Strong employee satisfaction' : parseFloat($json.avg_rating) >= 3.0 ? '⚠️ Average rating acceptable but room for improvement' : '❌ Low average rating - Immediate intervention required' }}\n\nNEXT STEPS:\n1. Share driver recognition list with HR for employee awards\n2. {{ $json.actionability_breakdown['Schedule Adjustment'] ? 'Meet with shuttle provider to discuss schedule optimization' : 'Continue monitoring schedule performance' }}\n3. {{ $json.actionability_breakdown['Add Capacity'] ? 'Analyze ridership data to justify additional capacity' : 'Monitor capacity trends' }}\n4. Review full feedback log in Google Sheets for detailed analysis\n5. Follow up on specific employee concerns within 48 hours\n\nGenerated: {{ new Date().toLocaleDateString() }}",
        "options": {}
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1056,
        -192
      ],
      "id": "1bcb7398-591c-424f-bad2-3f983a25c798",
      "name": "Send Weekly Feedback Report",
      "webhookId": "935a5039-f56f-451a-86f2-0993cf904e48",
      "credentials": {
        "gmailOAuth2": {
          "id": "cyqCGWcggZNMcSOv",
          "name": "Gmail account"
        }
      }
    }
  ],
  "pinData": {},
  "connections": {
    "Weekly Schedule - Monday 9 AM": {
      "main": [
        [
          {
            "node": "Scrape Shuttle Service Reviews",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Shuttle Service Reviews": {
      "main": [
        [
          {
            "node": "Get Dataset Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Dataset Items": {
      "main": [
        [
          {
            "node": "Validate Review Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Review Text": {
      "main": [
        [
          {
            "node": "AI: Analyze Shuttle Feedback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI: Analyze Shuttle Feedback": {
      "main": [
        [
          {
            "node": "Parse AI Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Response": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "Log to Google Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log to Google Sheet": {
      "main": [
        [
          {
            "node": "Aggregate Feedback Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Feedback Summary": {
      "main": [
        [
          {
            "node": "Send Weekly Feedback Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "a30868a8-5c9d-4346-9f1f-70b466c33010",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "3a43da28588548e21903e71cf1dc3ddd65c24bf0c62e7e4b77542ffe87ad79c6"
  },
  "id": "1At5KIvP3IsaqBnH",
  "tags": []
}